package cc.gehua.octopus.rpc.common.resource;

import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.ReadableByteChannel;
import java.util.ArrayList;
import java.util.Collection;
import cc.gehua.octopus.rpc.utils.IOUtil;
import com.sun.prism.ResourceFactory;
import java.net.URISyntaxException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/* ------------------------------------------------------------ */
/**
 * Abstract resource class.
 * <p>
 * This class provides a resource abstraction, where a resource may be a file, a
 * URL or an entry in a jar file.
 * </p>
 */
public abstract class Resource implements Closeable {

    private static final Logger LOG = LoggerFactory.getLogger(Resource.class);
    public static boolean defaultUseCaches = true;
    volatile Object _associate;

    /* ------------------------------------------------------------ */
    /**
     * Change the default setting for url connection caches. Subsequent
     * URLConnections will use this default.
     *
     * @param useCaches
     */
    public static void setDefaultUseCaches(boolean useCaches) {
        defaultUseCaches = useCaches;
    }

    /* ------------------------------------------------------------ */
    public static boolean getDefaultUseCaches() {
        return defaultUseCaches;
    }

    /* ------------------------------------------------------------ */
    /**
     * Construct a resource from a uri.
     *
     * @param uri A URI.
     * @return A Resource object.
     * @throws MalformedURLException Problem accessing URI
     */
    public static Resource newResource(URI uri)
            throws MalformedURLException {
        return newResource(uri.toURL());
    }

    /* ------------------------------------------------------------ */
    /**
     * Construct a resource from a url.
     *
     * @param url A URL.
     * @return A Resource object.
     */
    public static Resource newResource(URL url) {
        return newResource(url, defaultUseCaches);
    }

    /* ------------------------------------------------------------ */
    /**
     * Construct a resource from a url.
     *
     * @param url the url for which to make the resource
     * @param useCaches true enables URLConnection caching if applicable to the
     * type of resource
     * @return
     */
    static Resource newResource(URL url, boolean useCaches) {
        if (url == null) {
            return null;
        }

        String url_string = url.toExternalForm();
        if (url_string.startsWith("file:")) {
            try {
                FileResource fileResource = new FileResource(url);
                return fileResource;
            } catch (IOException | URISyntaxException e) {
                LOG.warn("Exception", e);
                return new BadResource(url, e.toString());
            }
        } else if (url_string.startsWith("jar:file:")) {
            return new JarFileResource(url, useCaches);
        } else if (url_string.startsWith("jar:")) {
            return new JarResource(url, useCaches);
        }

        return new URLResource(url, null, useCaches);
    }

    /* ------------------------------------------------------------ */
    /**
     * Construct a resource from a string.
     *
     * @param resource A URL or filename.
     * @throws MalformedURLException Problem accessing URI
     * @return A Resource object.
     */
    public static Resource newResource(String resource)
            throws MalformedURLException {
        return newResource(resource, defaultUseCaches);
    }

    /* ------------------------------------------------------------ */
    /**
     * Construct a resource from a string.
     *
     * @param resource A URL or filename.
     * @param useCaches controls URLConnection caching
     * @return A Resource object.
     * @throws MalformedURLException Problem accessing URI
     */
    public static Resource newResource(String resource, boolean useCaches)
            throws MalformedURLException {
        URL url = null;
        try {
            // Try to format as a URL?
            url = new URL(resource);
        } catch (MalformedURLException e) {
            if (!resource.startsWith("ftp:")
                    && !resource.startsWith("file:")
                    && !resource.startsWith("jar:")) {
                try {
                    // It's a file.
                    if (resource.startsWith("./")) {
                        resource = resource.substring(2);
                    }

                    File file = new File(resource).getCanonicalFile();
                    return new FileResource(file);
                } catch (IOException e2) {
                    LOG.debug("Exception", e2);
                    throw e;
                }
            } else {
                LOG.warn("Bad Resource: " + resource);
                throw e;
            }
        }

        return newResource(url);
    }

    /* ------------------------------------------------------------ */
    public static Resource newResource(File file) {
        return new FileResource(file);
    }

    /* ------------------------------------------------------------ */
    /**
     * Construct a system resource from a string. The resource is tried as
     * classloader resource before being treated as a normal resource.
     *
     * @param resource Resource as string representation
     * @return The new Resource
     * @throws IOException Problem accessing resource.
     */
    public static Resource newSystemResource(String resource)
            throws IOException {
        URL url = null;
        // Try to format as a URL?
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader != null) {
            try {
                url = loader.getResource(resource);
                if (url == null && resource.startsWith("/")) {
                    url = loader.getResource(resource.substring(1));
                }
            } catch (IllegalArgumentException e) {
                // Catches scenario where a bad Windows path like "C:\dev" is
                // improperly escaped, which various downstream classloaders
                // tend to have a problem with
                url = null;
            }
        }
        if (url == null) {
            loader = Resource.class.getClassLoader();
            if (loader != null) {
                url = loader.getResource(resource);
                if (url == null && resource.startsWith("/")) {
                    url = loader.getResource(resource.substring(1));
                }
            }
        }

        if (url == null) {
            url = ClassLoader.getSystemResource(resource);
            if (url == null && resource.startsWith("/")) {
                url = ClassLoader.getSystemResource(resource.substring(1));
            }
        }

        if (url == null) {
            return null;
        }

        return newResource(url);
    }

    /* ------------------------------------------------------------ */
    /**
     * Find a classpath resource.
     */
    public static Resource newClassPathResource(String resource) {
        return newClassPathResource(resource, true, false);
    }

    /* ------------------------------------------------------------ */
    /**
     * Find a classpath resource. The
     * {@link java.lang.Class#getResource(String)} method is used to lookup the
     * resource. If it is not found, then the
     * {@link Loader#getResource(Class, String)} method is used. If it is still
     * not found, then {@link ClassLoader#getSystemResource(String)} is used.
     * Unlike {@link ClassLoader#getSystemResource(String)} this method does not
     * check for normal resources.
     *
     * @param name The relative name of the resource
     * @param useCaches True if URL caches are to be used.
     * @param checkParents True if forced searching of parent Classloaders is
     * performed to work around loaders with inverted priorities
     * @return Resource or null
     */
    public static Resource newClassPathResource(String name, boolean useCaches, boolean checkParents) {
        URL url = Resource.class.getResource(name);

        if (url == null) {
            url = getResource(Resource.class, name);
        }
        if (url == null) {
            return null;
        }
        return newResource(url, useCaches);
    }

    public static URL getResource(Class<?> loadClass, String name) {
        URL url = null;
        ClassLoader context_loader = Thread.currentThread().getContextClassLoader();
        if (context_loader != null) {
            url = context_loader.getResource(name);
        }

        if (url == null && loadClass != null) {
            ClassLoader load_loader = loadClass.getClassLoader();
            if (load_loader != null && load_loader != context_loader) {
                url = load_loader.getResource(name);
            }
        }

        if (url == null) {
            url = ClassLoader.getSystemResource(name);
        }

        return url;
    }

    /* ------------------------------------------------------------ */
    public static boolean isContainedIn(Resource r, Resource containingResource) throws MalformedURLException {
        return r.isContainedIn(containingResource);
    }

    /* ------------------------------------------------------------ */
    @Override
    protected void finalize() throws Throwable {
        close();
        super.finalize();
    }

    /* ------------------------------------------------------------ */
    public abstract boolean isContainedIn(Resource r) throws MalformedURLException;

    /* ------------------------------------------------------------ */
    /**
     * Release any temporary resources held by the resource.
     *
     * @deprecated use {@link #close()}
     */

    /* ------------------------------------------------------------ */
    /**
     * Release any temporary resources held by the resource.
     */
    @Override
    public abstract void close();

    /* ------------------------------------------------------------ */
    /**
     * Returns true if the respresened resource exists.
     * @return 
     */
    public abstract boolean exists();


    /* ------------------------------------------------------------ */
    /**
     * Returns true if the respresenetd resource is a container/directory. If
     * the resource is not a file, resources ending with "/" are considered
     * directories.
     */
    public abstract boolean isDirectory();

    /* ------------------------------------------------------------ */
    /**
     * Returns the last modified time
     */
    public abstract long lastModified();


    /* ------------------------------------------------------------ */
    /**
     * Return the length of the resource
     */
    public abstract long length();


    /* ------------------------------------------------------------ */
    /**
     * Returns an URL representing the given resource
     */
    // TODO: should deprecate this one and only use getURI()
    public abstract URL getURL();

    /* ------------------------------------------------------------ */
    /**
     * Returns an URI representing the given resource
     * @return 
     */
    public URI getURI() {
        try {
            return getURL().toURI();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }


    /* ------------------------------------------------------------ */
    /**
     * Returns an File representing the given resource or NULL if this is not
     * possible.
     * @return 
     * @throws java.io.IOException 
     */
    public abstract File getFile()
            throws IOException;


    /* ------------------------------------------------------------ */
    /**
     * Returns the name of the resource
     * @return 
     */
    public abstract String getName();


    /* ------------------------------------------------------------ */
    /**
     * Returns an input stream to the resource
     * @return 
     * @throws java.io.IOException 
     */
    public abstract InputStream getInputStream()
            throws java.io.IOException;

    /* ------------------------------------------------------------ */
    /**
     * Returns an readable bytechannel to the resource or null if one is not
     * available.
     * @return 
     * @throws java.io.IOException 
     */
    public abstract ReadableByteChannel getReadableByteChannel()
            throws java.io.IOException;

    /* ------------------------------------------------------------ */
    /**
     * Deletes the given resource
     * @return 
     */
    // TODO: can throw IOException
    public abstract boolean delete()
            throws SecurityException;

    /* ------------------------------------------------------------ */
    /**
     * Rename the given resource
     * @param dest
     * @return 
     */
    // TODO: can throw IOException
    public abstract boolean renameTo(Resource dest)
            throws SecurityException;

    /* ------------------------------------------------------------ */
    /**
     * Returns a list of resource names contained in the given resource The
     * resource names are not URL encoded.
     * @return 
     */
    // TODO: can throw IOException
    public abstract String[] list();

    /* ------------------------------------------------------------ */
    /**
     * Returns the resource contained inside the current resource with the given
     * name.
     *
     * @param path The path segment to add, which is not encoded
     * @return 
     * @throws java.io.IOException 
     * @throws java.net.MalformedURLException 
     */
    public abstract Resource addPath(String path)
            throws IOException, MalformedURLException;

    /* ------------------------------------------------------------ */
    /**
     * Get a resource from within this resource.
     * <p>
     * This method is essentially an alias for {@link #addPath(String)}, but
     * without checked exceptions. This method satisfied the
     * {@link ResourceFactory} interface.
     *
     * @param path
     * @return 
     * @see
     * org.eclipse.jetty.util.resource.ResourceFactory#getResource(java.lang.String)
     */
    public Resource getResource(String path) {
        try {
            return addPath(path);
        } catch (IOException e) {
            LOG.debug("Get resource exception", e);
            return null;
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * @return 
     * @deprecated
     */
    /* ------------------------------------------------------------ */
    public Object getAssociate() {
        return _associate;
    }

    /* ------------------------------------------------------------ */
    public void setAssociate(Object o) {
        _associate = o;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return The canonical Alias of this resource or null if none.
     */
    /* ------------------------------------------------------------ */
    /**
     * @param out
     * @param start First byte to write
     * @param count Bytes to write or -1 for all of them.
     * @throws java.io.IOException
     */
    public void writeTo(OutputStream out, long start, long count)
            throws IOException {
        try (InputStream in = getInputStream()) {
            long skip = in.skip(start);
            if (count < 0) {
                IOUtil.copy(in, out);
            } else {
                IOUtil.copy(in, out, count);
            }
            if(skip < 0){
                LOG.debug("Skip?");
            }
        }
    }

    /* ------------------------------------------------------------ */
    public void copyTo(File destination)
            throws IOException {
        if (destination.exists()) {
            throw new IllegalArgumentException(destination + " exists");
        }
        try (OutputStream out = new FileOutputStream(destination)) {
            writeTo(out, 0, -1);
        }
    }

    /* ------------------------------------------------------------ */
    public Collection<Resource> getAllResources() {
        try {
            ArrayList<Resource> deep = new ArrayList<>();
            {
                String[] list = list();
                if (list != null) {
                    for (String i : list) {
                        Resource r = addPath(i);
                        if (r.isDirectory()) {
                            deep.addAll(r.getAllResources());
                        } else {
                            deep.add(r);
                        }
                    }
                }
            }
            return deep;
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Generate a properly encoded URL from a {@link File} instance.
     *
     * @param file Target file.
     * @return URL of the target file.
     * @throws MalformedURLException
     */
    public static URL toURL(File file) throws MalformedURLException {
        return file.toURI().toURL();
    }
}
